/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import {
  ts,
  AstTreeUtils,
  ArkFile,
  Local,
  ArkAssignStmt,
  AbstractFieldRef,
  ClassSignature,
  ArkClass,
  Scene,
  ArkField,
  LineColPosition,
  Value,
  Stmt,
} from 'arkanalyzer/lib';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import {
  FileMatcher,
  MatcherCallback,
  MatcherTypes,
} from '../../matcher/Matchers';
import { RuleListUtil } from '../../utils/common/DefectsList';
import { Defects, IssueReport } from '../../model/Defects';
import { Rule } from '../../model/Rule';
import { Utils } from '../../utils/common/Utils';
import { RuleFix } from '../../model/Fix';
//methodParams
type Options = {
  allowPrivateClassPropertyAccess: boolean;
  allowProtectedClassPropertyAccess: boolean;
  allowIndexSignaturePropertyAccess: boolean;
  allowKeywords: boolean;
  allowPattern: string;
};

type posionS = {
  startLine: number;
  startCol: number;
  endLine: number;
  endCol: number;
};

const defaultOptions1: Options = {
  allowPrivateClassPropertyAccess: false,
  allowProtectedClassPropertyAccess: false, //默认fail
  allowIndexSignaturePropertyAccess: false, //默认fail
  allowKeywords: true,
  allowPattern: '',
};

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'DotNotationCheck');
const matchL = /\b\w+\s*\.\s*([\w$]+)\b/;
const matchR = /\b\w+\s*\[\s*(['"])(.*?)\1\s*\]/;
const gMetaData: BaseMetaData = {
  severity: 1,
  ruleDocPath: 'docs/dot-notation-check.md',
  description: 'is better written in dot notation.',
};
//要求或不允许函数标识符和它们的调用之间有空格
export class DotNotationCheck implements BaseChecker {
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  private issueMap: Map<string, IssueReport> = new Map();
  private fileMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  public registerMatchers(): MatcherCallback[] {
    const fileMatchBuildCb: MatcherCallback = {
      matcher: this.fileMatcher,
      callback: this.check,
    };
    return [fileMatchBuildCb];
  }

  public check = (targetFile: ArkFile) => {
    const filePath = targetFile.getName();
    const isTs = this.getFileExtension(filePath, 'ts');
    if (!isTs) {
      return;
    }
    let options = (this.rule.option[0] as Options) || defaultOptions1;
    let mergedOptions: Options = {
      ...defaultOptions1,
      ...options,
    };
    const scene = targetFile.getScene();
    const classes = targetFile.getClasses();
    let arfileCode = targetFile.getCode();
    const arkfilePath = targetFile.getFilePath();
    this.issueMap.clear();
    this.checkClassforMethod(
      scene,
      arfileCode,
      arkfilePath,
      classes,
      mergedOptions
    );
    this.reportSortedIssues();
  };

  checkIndexSignature(node: ts.Node): boolean {
    // 如果当前节点是索引签名声明，直接返回 true
    if (ts.isIndexSignatureDeclaration(node)) {
      return true;
    }
    // 获取当前节点的子节点
    const children = node.getChildren();
    // 遍历子节点并递归检查
    for (const child of children) {
      // 如果在子节点中找到索引签名声明，立刻返回 true
      if (this.checkIndexSignature(child)) {
        return true;
      }
    }
    // 如果没有找到索引签名声明，返回 false
    return false;
  }

  getdeclareNode(
    childr: ts.Node | undefined,
    program: ts.Program
  ): ts.Node | undefined {
    if (!childr) {
      return undefined;
    }
    const propertySymbol = program.getTypeChecker().getSymbolAtLocation(childr);
    const cvb = propertySymbol?.declarations?.[0] ?? undefined;
    // 如果是类声明
    if (!cvb) {
      return undefined;
    }
    return cvb;
  }

  // 判断一个字符串是否为关键字或操作符
  private isKeyword(str: string): boolean {
    const keywords = [
      'if', 'else', 'for', 'while', 'switch', 'case', 'break', 'continue', 'return', 'function',
      'class', 'const', 'let', 'var', 'try', 'catch', 'finally', 'throw', 'new', 'this', 'super',
      'import', 'export', 'default', 'void', 'undefined', 'null', 'true', 'false', 'await', 'async',
      'instanceof', 'typeof', 'in', 'debugger', 'yield', 'eval', 'arguments', 'delete', 'any', 
      'unknown', 'never', 'type', 'interface', 'enum', 'as', 'extends', 'implements', 'infer', 'declare',
      'module', 'namespace', 'public', 'private', 'protected', 'readonly'
    ];
    return keywords.includes(str);
  }

  private addIssueReport(
    arkFilePath: string,
    line: number,
    startCol: number,
    endCol: number,
    name: string,
    message: string
  ): Defects {
    const severity = this.rule.alert ?? this.metaData.severity;
    const defect = new Defects(
      line,
      startCol,
      endCol,
      message,
      severity,
      this.rule.ruleId,
      arkFilePath,
      this.metaData.ruleDocPath,
      true,
      false,
      false
    );
    this.defects.push(defect);
    return defect;
  }

  private ruleFix(pos: number, end: number, text: string): RuleFix {
    return { range: [pos, end], text: text };
  }

  private reportSortedIssues(): void {
    if (this.issueMap.size === 0) {
      return;
    }

    const sortedIssues = Array.from(this.issueMap.entries()).sort(
      ([keyA], [keyB]) => Utils.sortByLineAndColumn(keyA, keyB)
    );

    this.issues = [];

    sortedIssues.forEach(([_, issue]) => {
      RuleListUtil.push(issue.defect);
      this.issues.push(issue);
    });
  }

  private getFileExtension(filePath: string, filetype: string): boolean {
    // 使用正则表达式匹配文件后缀
    const match = filePath.match(/\.([0-9a-zA-Z]+)$/);

    // 如果匹配到了扩展名，且扩展名等于 filetype，则返回扩展名，否则返回空字符串
    if (match) {
      const extension = match[1];
      return extension === filetype;
    }

    return false;
  }

  //判断是否是[]调用
  private fieldUsed(origText: string, fieldname: string): posionS {
    // 转义字段，避免正则特殊字符冲突
    const escapedField = fieldname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    // 正则匹配 `obj['property']` 或 `obj['property']`
    const bracketRegex = new RegExp(
      `(\\w+)\\s*\\[\\s*(['"])${escapedField}\\2\\s*\\]`,
      'g'
    );
    // 正则匹配 `obj.property`
    const dotRegex = new RegExp(`(\\w+)\\s*\\.\\s*${escapedField}\\b`, 'g');
    let result: posionS = { startLine: 0, startCol: 0, endLine: 0, endCol: 0 };
    let match;
    // 处理 `obj['property']`
    while ((match = bracketRegex.exec(origText)) !== null) {
      const bracketStartIdx = match.index + match[0].indexOf('['); // `[` 的索引
      const bracketEndIdx = match.index + match[0].lastIndexOf(']'); // `]` 的索引
      // 计算 `[` 的行列号
      const beforeStart = origText.substring(0, bracketStartIdx);
      const startLines = beforeStart.split('\n');
      result.startLine = startLines.length;
      result.startCol = startLines[startLines.length - 1].length + 1;
      // 计算 `]` 的行列号
      const beforeEnd = origText.substring(0, bracketEndIdx + 1);
      const endLines = beforeEnd.split('\n');
      result.endLine = endLines.length;
      result.endCol = endLines[endLines.length - 1].length + 1;
    }
    // 处理 `obj.property`
    while ((match = dotRegex.exec(origText)) !== null) {
      const dotIdx = match.index + match[0].indexOf('.'); // `.` 的索引
      const propEndIdx = dotIdx + 1 + fieldname.length; // `property` 结束的索引
      // 计算 `.` 的行列号
      const beforeDot = origText.substring(0, dotIdx);
      const dotLines = beforeDot.split('\n');
      result.startLine = dotLines.length;
      result.startCol = dotLines[dotLines.length - 1].length + 1;
      // 计算 `property` 结束的行列号
      const beforePropEnd = origText.substring(0, propEndIdx);
      const propEndLines = beforePropEnd.split('\n');
      result.endLine = propEndLines.length;
      result.endCol = propEndLines[propEndLines.length - 1].length + 1;
    }
    // 如果没有匹配到任何方式，返回 0
    return result;
  }

  private addFixByDot(
    filePath: string,
    declareOrigtext: string,
    fieldName: string,
    start: number,
    lineStartNo: number,
    lineStartCol: number,
    posionFields: posionS
  ): void {
    if (!this.isKeyword(fieldName)) {
      return;
    } // 先检查是否为关键词，避免不必要遍历
    const astRoot = AstTreeUtils.getASTNode(fieldName, declareOrigtext);
    const matchingNodes = this.collectMatchingNodes(astRoot);
    for (const chd of matchingNodes) {
      if (!ts.isPropertyAccessExpression(chd)) {
        continue;
      } // 只处理 `obj.key` 形式
      for (const node of chd.getChildren()) {
        if (node.getText() !== fieldName) {
          continue;
        } // 过滤掉非匹配字段名
        const startOffset = start + node.getStart();
        const endOffset = start + node.getEnd();
        const startColOffset = lineStartCol + posionFields.startCol;
        const defect = this.addIssueReport(
          filePath,
          lineStartNo,
          startColOffset,
          startColOffset + fieldName.length,
          `.${fieldName}`,
          `.${fieldName} ` + this.metaData.description
        );
        const fix: RuleFix = this.ruleFix(
          startOffset,
          endOffset,
          `['${fieldName}']`
        );
        defect.fixable = true;
        this.issueMap.set(defect.fixKey, { defect, fix });
      }
    }
  }

  private addFix(
    filePath: string,
    declareOrigtext: string,
    fieldName: string,
    start: number,
    lineStartNo: number,
    lineStartCol: number,
    posionFields: posionS
  ): void {
    const startColOffset = lineStartCol + (posionFields?.startCol ?? 0);
    const astRoot = AstTreeUtils.getASTNode(fieldName, declareOrigtext);
    const matchingNodes = this.collectMatchingNodes(astRoot);
    for (const chd of matchingNodes) {
      if (!ts.isElementAccessExpression(chd)) {
        continue;
      } // 只处理 obj['key'] 形式
      const [
        expression,
        openBracketToken,
        argumentExpression,
        closeBracketToken,
      ] = chd.getChildren();
      if (!ts.isStringLiteral(argumentExpression)) {
        continue;
      } // 确保是字符串字面量
      const defect = this.addIssueReport(
        filePath,
        lineStartNo,
        startColOffset,
        startColOffset + fieldName.length,
        `[${fieldName}]`,
        `[${fieldName}] ` + this.metaData.description
      );
      const fix: RuleFix = this.ruleFix(
        start + openBracketToken.getStart(),
        start + closeBracketToken.getEnd(),
        `.${fieldName}`
      );
      defect.fixable = true;
      this.issueMap.set(defect.fixKey, { defect, fix });
    }
  }

  private collectMatchingNodes(astRoot: ts.Node): ts.Node[] {
    const matchingNodes: ts.Node[] = [];
    astRoot.forEachChild((child) =>
      matchingNodes.push(...this.collectNodes(child))
    );
    return matchingNodes;
  }
  private extractPropertyName(origText: string): string {
    const matchLr = new RegExp(matchL);
    const matchRr = new RegExp(matchR);
    return this.matchProperty(origText, matchLr, 1) ||
           this.matchProperty(origText, matchRr, 2) ||
           'errorProperty';
}

private matchProperty(text: string, regex: RegExp, groupIndex: number): string | null {
  const match = text.match(regex);
  return match ? match[groupIndex] : null;
}

  //检查所有的节点
  private collectNodes(node: ts.Node): ts.Node[] {
    const matchingNodes: ts.Node[] = [];
    // 如果节点是 ElementAccessExpression，添加到数组
    if (ts.isElementAccessExpression(node)) {
      matchingNodes.push(node);
    }
    // 如果节点是 PropertyAccessExpression，添加到数组
    if (ts.isPropertyAccessExpression(node)) {
      matchingNodes.push(node);
    }
    // 递归遍历所有子节点
    ts.forEachChild(node, (childNode) => {
      // 将子节点符合条件的节点合并到当前匹配节点数组
      matchingNodes.push(...this.collectNodes(childNode));
    });
    return matchingNodes;
  }

  //获取分割符
  private getLineBreak(text: string): string {
    if (text.includes('\r\n')) {
      return '\r\n';
    } else if (text.includes('\n')) {
      return '\n';
    } else {
      return '\r';
    }
  }

  private checkClassforMethod(
    scene: Scene,
    arfilecode: string,
    arkfilePath: string,
    classes: ArkClass[],
    mergedOptions: Options
  ): void {
    for (const clas of classes) {
      const methods = clas.getMethods();
      for (const med of methods) {
        const varLocal: Map<string, Local> =
          med.getBody()?.getLocals() ?? new Map<string, Local>();
        this.checkLocal(
          varLocal,
          scene,
          arfilecode,
          arkfilePath,
          mergedOptions
        );
      }
    }
  }

  private checkLocal(
    varLocal: Map<string, Local>,
    scene: Scene,
    arfilecode: string,
    arkfilePath: string,
    mergedOptions: Options
  ): void {
    const regex = new RegExp(mergedOptions.allowPattern);
    // 找到当前分割符所在行
    let lineBreak = this.getLineBreak(arfilecode);
    for (const [key, local] of varLocal) {
      const declareStmt = local.getDeclaringStmt();
      const position = declareStmt?.getOriginPositionInfo();
      if (declareStmt instanceof ArkAssignStmt && position) {
        const rightOp = declareStmt.getRightOp();
        this.exceRightOp(
          rightOp,
          declareStmt,
          scene,
          position,
          arfilecode,
          arkfilePath,
          lineBreak,
          mergedOptions,
          regex
        );
      }
    }
  }

  private addCollectaboutClass(
    arfilecode: string,
    arkfilePath: string,
    arkField: ArkField,
    declareOrigtext: string,
    position: LineColPosition,
    lineBreak: string,
    mergedOptions: Options,
    regex: RegExp
  ): void {
    const allowKeywords = mergedOptions.allowKeywords;
    const fieldName = arkField?.getName();
    const isPrivate = arkField?.isPrivate();
    const isProtected = arkField?.isProtected();
    const posionFields = this.fieldUsed(declareOrigtext, fieldName);
    const lineStartNo = position?.getLineNo() ?? -1;
    const lineStartCol = position?.getColNo() ?? -1;
    let cnt = 0;
    for (let index = 1; index < lineStartNo; index++) {
      cnt = arfilecode.indexOf(lineBreak, cnt + 1);
    }
    const start = cnt === 0 && lineStartNo === 1 ? 0 : cnt + lineStartCol + 1;
    if (allowKeywords) {
      if ( mergedOptions.allowPrivateClassPropertyAccess && isPrivate) { 
        return; 
      } 
      else if (mergedOptions.allowProtectedClassPropertyAccess && isProtected) { 
        return; 
      } 
      else if (!(mergedOptions.allowPattern !== '' && regex.test(fieldName))) {
        this.addFix( arkfilePath,
          declareOrigtext,
          fieldName,
          start,
          lineStartNo,
          lineStartCol,
          posionFields
        );
      }
    } else {
      this.addFixByDot( arkfilePath,
        declareOrigtext,
        fieldName,
        start,
        lineStartNo,
        lineStartCol,
        posionFields
      );
    }
  }

  private addCollectOther(
    arfilecode: string,
    declareOrigtext: string,
    arkfilePath: string,
    lineBreak: string,
    position: LineColPosition,
    mergedOptions: Options,
    regex: RegExp,
    clas: boolean
  ): void {
    const allowKeywords = mergedOptions.allowKeywords;
    const allowIndexSignaturePropertyAccess = mergedOptions.allowIndexSignaturePropertyAccess;
    const fieldName = this.extractPropertyName(declareOrigtext);
    const posionFieldst = this.fieldUsed(declareOrigtext, fieldName);
    const lineStartNot = position?.getLineNo() ?? -1;
    const lineStartColt = position?.getColNo() ?? -1;
    let cntt = 0;
    for (let index = 1; index < lineStartNot; index++) {
      cntt = arfilecode.indexOf(lineBreak, cntt + 1);
    }
    //对第一行第一列特殊处理，后续代码都是以0为起始偏移，所以需要+1
    const start = cntt === 0 && lineStartNot === 1 ? 0 : cntt + lineStartColt + 1;
    if (allowKeywords) {
      if (allowIndexSignaturePropertyAccess && clas) {
        return;
      } else if (!(mergedOptions.allowPattern !== '' && regex.test(fieldName))) {
        this.addFix(
          arkfilePath,
          declareOrigtext,
          fieldName,
          start,
          lineStartNot,
          lineStartColt,
          posionFieldst
        );
      }
    } else {
      this.addFixByDot(
        arkfilePath,
        declareOrigtext,
        fieldName,
        start,
        lineStartNot,
        lineStartColt,
        posionFieldst
      );
    }
  }

  private exceRightOp(
    rightOp: Value,
    declareStmt: Stmt,
    scene: Scene,
    position: LineColPosition,
    arfilecode: string,
    arkfilePath: string,
    lineBreak: string,
    mergedOptions: Options,
    regex: RegExp
  ): void {
    if (rightOp instanceof AbstractFieldRef) {
      const fieldsign = rightOp.getFieldSignature();
      const classSign = fieldsign.getDeclaringSignature();
      const declareOrigtext = declareStmt.getOriginalText() ?? '';
      if (classSign instanceof ClassSignature) {
        const clas = scene.getClass(classSign);
        const arkField = clas?.getField(fieldsign);
        //有定义class的
        if (arkField && position) {
          this.addCollectaboutClass(
            arfilecode,
            arkfilePath,
            arkField,
            declareOrigtext,
            position,
            lineBreak,
            mergedOptions,
            regex
          );
        } else {
          //没有class类的
          const isclass = clas ? true : false;
          this.addCollectOther(
            arfilecode,
            declareOrigtext,
            arkfilePath,
            lineBreak,
            position,
            mergedOptions,
            regex,
            isclass
          );
        }
      }
    }
  }
}
